package xyz.xtt.equity.service.impl;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import xyz.xtt.equity.constants.ErrorMessage;
import xyz.xtt.equity.entity.TblAssetEntitlement;
import xyz.xtt.equity.entity.TblAssetEntitlementDeliver;
import xyz.xtt.equity.entity.TblLuckPhoneEntitlementDeliver;
import xyz.xtt.equity.entity.TblLuckPhonePool;
import xyz.xtt.equity.exception.ChainException;
import xyz.xtt.equity.mapper.TblAssetEntitlementMapper;
import xyz.xtt.equity.mapper.TblLuckPhoneEntitlementDeliverMapper;
import xyz.xtt.equity.request.TlbLuckPhoneOrderReq;
import xyz.xtt.equity.response.TlbLuckPhoneOrderRes;
import xyz.xtt.equity.service.ITblAssetEntitlementDeliverService;
import xyz.xtt.equity.service.ITblAssetEntitlementService;
import xyz.xtt.equity.service.ITblLuckPhoneEntitlementDeliverService;
import xyz.xtt.equity.service.ITblLuckPhonePoolService;
import xyz.xtt.equity.support.GomeLuckPhoneHttpService;
import xyz.xtt.equity.utils.RedisLockUtil;
import xyz.xtt.equity.vo.LuckPhoneDeliverVo;

/**
 * 靓号兑换订单记录服务实现类
 *
 * @author dale
 * @since 2024/12/31
 **/
@Service
@Slf4j
public class TblLuckPhoneEntitlementDeliverServiceImpl extends ServiceImpl<TblLuckPhoneEntitlementDeliverMapper, TblLuckPhoneEntitlementDeliver>
		implements ITblLuckPhoneEntitlementDeliverService {
	private static final Integer ORDER_STATUS_REFUND = 2;
	private static final Integer ORDER_STATUS_FAILED = 11;
	private static final String LUCK_PHONE_ORDER_LOCK_KEY = "NFT:LOCK:LUCK:PHONE:ORDER:%s:%d";
	private static final String PHONE_LOCK_KEY = "NFT:LOCK:LUCK:PHONE:%s";
	@Autowired
	private GomeLuckPhoneHttpService gomeLuckPhoneHttpService;
	@Autowired
	private TblAssetEntitlementMapper assetEntitlementMapper;
	@Autowired
	private ITblAssetEntitlementService assetEntitlementService;
	@Autowired
	private ITblAssetEntitlementDeliverService iAssetEntitlementDeliverService;
	@Autowired
	private RedisLockUtil redisLockUtil;
	@Autowired
	private ITblLuckPhonePoolService tblLuckPhonePoolService;
	@Autowired
	private StringRedisTemplate redisTemplate;

	/**
	 * 提交靓号订单
	 *
	 * @param uid 用户id
	 * @param req 请求参数
	 * @return 订单信息
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public TlbLuckPhoneOrderRes submitLuckPhoneOrder(String uid, TlbLuckPhoneOrderReq req) {
		String receivePhone = req.getReceiverPhone();
		// 正则校验11位电话号码格式
		if (!receivePhone.matches("^\\d{11}$")) {
			throw new ChainException(ErrorMessage.PHONE_NUMBER_FORMAT_ERROR);
		} else if (!req.isAgreementAccepted()) {
			throw new ChainException(ErrorMessage.AGREEMENT_NOT_ACCEPTED);
		}
		log.info("submitLuckPhoneOrder uid:{} req:{}", uid, JSONUtil.toJsonStr(req));

		// 校验权益是否存在
		Long entitlementId = Long.parseLong(req.getEntitlementId());
		TblAssetEntitlement tblAssetEntitlement = assetEntitlementMapper.selectById(entitlementId);
		if (tblAssetEntitlement == null) {
			log.error("提交靓号订单失败，权益不存在。用户ID：{} 权益id：{}", uid, entitlementId);
			throw new ChainException(ErrorMessage.ARGUMENT_NOT_VALID);
		}
		// 校验是否已兑换权益
		TblAssetEntitlementDeliver entitlementDeliver = iAssetEntitlementDeliverService.info(entitlementId, uid);
		if (Objects.isNull(entitlementDeliver)) {
			throw new ChainException(ErrorMessage.EQUITY_EXCHANGE_USER_WRONG);
		}

		// 号码占用并发控制
		String phoneLockKey = String.format(PHONE_LOCK_KEY, req.getLuckPhone());
		String phoneLockValue = UUID.randomUUID().toString();
		if (!redisLockUtil.tryLock(phoneLockKey, phoneLockValue, 1, TimeUnit.MINUTES)) {
			throw new ChainException(ErrorMessage.LUCK_PHONE_LOCK_FAIL);
		}
		try {
			long orderNo = IdUtil.getSnowflakeNextId();
			long now = System.currentTimeMillis();
			// 用户提交订单并发控制
			String lockKey = String.format(LUCK_PHONE_ORDER_LOCK_KEY, uid, entitlementId);
			String lockValue = UUID.randomUUID().toString();
			boolean locked = redisLockUtil.tryLock(lockKey, lockValue, 1, TimeUnit.MINUTES, 5, TimeUnit.SECONDS);
			if (locked) {
				try {
					// 校验是否已经成功获取靓号
					TblLuckPhoneEntitlementDeliver validOrder = queryValidOrder(uid, entitlementId);
					if (validOrder != null && validOrder.getOrderStatus() != ORDER_STATUS_REFUND && validOrder.getOrderStatus() != ORDER_STATUS_FAILED) {
						throw new ChainException(ErrorMessage.EQUITYS_NUMBER_ZERO);
					} else if (validOrder != null) {
						UpdateWrapper<TblLuckPhoneEntitlementDeliver> updateWrapper = new UpdateWrapper<>();
						updateWrapper.eq("id", validOrder.getId());
						updateWrapper.set("is_valid", 0);
						baseMapper.update(updateWrapper);
					}

					// 保存本地订单记录
					TblLuckPhoneEntitlementDeliver luckPhoneEntitlementDeliver = TblLuckPhoneEntitlementDeliver
							.builder()
							.id(IdUtil.getSnowflakeNextId())
							.luckPhone(req.getLuckPhone())
							.appUserId(Long.valueOf(uid))
							.receiverPhone(req.getReceiverPhone())
							.orderNo(orderNo)
							.assetId(tblAssetEntitlement.getAssetId())
							.entitlementId(tblAssetEntitlement.getId())
							.orderStatus(0)
							.isValid(true)
							.createTime(now)
							.updateTime(now)
							.build();
					save(luckPhoneEntitlementDeliver);
				} finally {
					redisLockUtil.releaseLock(lockKey, lockValue);
				}
			} else {
				log.error("提交靓号订单失败，无法获取分布式锁。用户ID：{}", uid);
				throw new ChainException(ErrorMessage.ERROR_GENERAL);
			}

			try {
				// 电话预占
				gomeLuckPhoneHttpService.preLockPhone(req.getLuckPhone());

				// 提交订单
				gomeLuckPhoneHttpService.submitOrder(req.getLuckPhone(), req.getReceiverPhone(), orderNo, "nft");
				removeAlreadyUsedPhone(req.getLuckPhone());
			} catch (ChainException e) {
				if (Objects.equals(e.getCode(), ErrorMessage.LUCK_PHONE_CAN_NOT_USE.getCode())
						|| Objects.equals(e.getCode(), ErrorMessage.LUCK_PHONE_LOCK_FAIL.getCode())) {
					// 号码暂不可用处理
					removeAlreadyUsedPhone(req.getLuckPhone());
				}
				throw e;
			}

			TlbLuckPhoneOrderRes res = new TlbLuckPhoneOrderRes();
			res.setOrderNo(String.valueOf(orderNo));
			res.setLuckPhone(req.getLuckPhone());
			res.setReceiverPhone(req.getReceiverPhone());
			return res;
		} finally {
			redisLockUtil.releaseLock(phoneLockKey, phoneLockValue);
		}
	}

	private void removeAlreadyUsedPhone(String luckPhone) {
		QueryWrapper<TblLuckPhonePool> query = new QueryWrapper<>();
		query.eq("tele_phone", luckPhone);
		tblLuckPhonePoolService.remove(query);
	}

	/**
	 * 查询有效订单
	 *
	 * @param uid
	 * @param entitlementId
	 * @return
	 */
	private TblLuckPhoneEntitlementDeliver queryValidOrder(String uid, Long entitlementId) {
		QueryWrapper<TblLuckPhoneEntitlementDeliver> query = new QueryWrapper<>();
		query.eq("app_user_id", uid).eq("entitlement_id", entitlementId).eq("is_valid", 1);
		return baseMapper.selectOne(query);
	}

	@Override
	public LuckPhoneDeliverVo getLuckPhoneOrder(String uid) {
		long lUid = Long.parseLong(uid);
		LambdaQueryChainWrapper<TblLuckPhoneEntitlementDeliver> query = new LambdaQueryChainWrapper<>(baseMapper);

		query.eq(TblLuckPhoneEntitlementDeliver::getAppUserId, lUid).eq(TblLuckPhoneEntitlementDeliver::getIsValid, 1);
		TblLuckPhoneEntitlementDeliver one = baseMapper.selectOne(query.getWrapper());
		if (null == one) {
			throw new ChainException(ErrorMessage.LUCK_ORDER_NULL);
		}
		Integer orderstatus = -1;

		// 只查询 7天之内的数据是否需要再次调用接口
		// 手机号订单状态查询接口
		// xyz.xtt.exchange.support.GomeLuckPhoneHttpService#fetchOrderStatus
		if (System.currentTimeMillis() - one.getUpdateTime() < 7 * 86400 * 1000 && one.getOrderStatus() != 2 && one.getOrderStatus() != 3
				&& one.getOrderStatus() != 11) {
			orderstatus = gomeLuckPhoneHttpService.fetchOrderStatus(one.getOrderNo());
			if (!orderstatus.equals(one.getOrderStatus())) {
				UpdateWrapper<TblLuckPhoneEntitlementDeliver> updateWrapper = new UpdateWrapper<>();
				updateWrapper.eq("id", one.getId());
				updateWrapper.set("order_status", orderstatus);
				updateWrapper.set("update_time", System.currentTimeMillis());
				baseMapper.update(updateWrapper);
			}
		}
		int status = 0;
		if (orderstatus != null && !orderstatus.equals(-1)) {
			one.setOrderStatus(orderstatus);
		}
		if (2 == one.getOrderStatus() || 11 == one.getOrderStatus()) {
			status = 1;
		}
		return new LuckPhoneDeliverVo(one.getLuckPhone(), one.getReceiverPhone(), one.getOrderNo(), status);
	}
}
